﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Renci.SshNet;
using Renci.SshNet.Common;

namespace PATBController
{
    public class MachineClass
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string File { get; set; }
        public string GuestOS { get; set; }
        public string Version { get; set; }
        public string Annotation { get; set; }
    }
    class VMwareESXiHandler : IDisposable
    {
        private bool _disposed;
        private SshClient virtualHost = null;
        private string powerState;

        public string VMPOWERSTATE_POWEREDON { get; private set; }
        public string VMPOWERSTATE_POWEREDOFF { get; private set; }
        public string hostAddress { get; private set; }
        public bool hostConnected { get; private set; }
        public bool machineSelected { get; private set; }
        public string selectedMachine { get; private set; }
        public int selectedMachineId { get; private set; }
        public string assignedIp { get; private set; }
        public Dictionary<string, string> machineProperties { get; private set; }
        public Dictionary<string, MachineClass> hostMachines { get; private set; }
        public VMwareESXiHandler(string HostAddress)
        {
            hostAddress = HostAddress;
            hostConnected = false;
            machineSelected = false;
            selectedMachine = "";
            selectedMachineId = -1;
            powerState = "";
            assignedIp = "";
            machineProperties = new Dictionary<string, string>();

            _disposed = true;

            VMPOWERSTATE_POWEREDON = @"poweredOn";
            VMPOWERSTATE_POWEREDOFF = @"poweredOff";

        }
        public bool IsPoweredOn()
        {
            return (powerState == VMPOWERSTATE_POWEREDON);
        }
        public string PowerState()
        {
            if (!machineSelected)
                return ("");

            // update properties
            PopulateMachineProperties();
            return (powerState);
        }
        public bool Connect(string User, string Password)
        {
            if (hostConnected)
                return (true);

            KeyboardInteractiveConnectionInfo connInfo = new KeyboardInteractiveConnectionInfo(hostAddress, User);
            connInfo.AuthenticationPrompt += delegate(object connSender, AuthenticationPromptEventArgs conne)
            {
                foreach (var prompt in conne.Prompts)
                {
                    if (prompt.Request.Equals("Password: ", StringComparison.InvariantCultureIgnoreCase))
                    {
                        prompt.Response = Password;
                    }
                }
            };

            virtualHost = new SshClient(connInfo);
            virtualHost.Connect();
            if (virtualHost.IsConnected)
            {
                hostConnected = true;
                PopulateMachineList();
                _disposed = false;
            }
            else
            {
                virtualHost.Dispose();
            }

            return (hostConnected);
        }
        private void PopulateMachineList()
        {
            hostMachines = new Dictionary<string, MachineClass>();

            // get list of machines
            // yes, this is ugly, but hey, I'm parsing console output!!
            using (SshCommand cmd = virtualHost.RunCommand("vim-cmd vmsvc/getallvms"))
            {
                String[] sResLines = cmd.Result.Split("\n".ToCharArray(0, 1)[0]);
                if (sResLines.GetLength(0) > 2)
                {

                    int lIndex = 0;
                    while (++lIndex < sResLines.GetLength(0))
                    {
                        if (sResLines[lIndex].Trim().Length > 50)
                        {
                            MachineClass item = new MachineClass();
                            string sLine = sResLines[lIndex];
                            // find end of first column
                            int iCharEndIndex = sLine.IndexOf("   ");
                            try
                            {
                                string sVal = sLine.Substring(0, iCharEndIndex).Trim();
                                if (char.IsDigit(sLine.ToCharArray()[0]))
                                    item.Id = Convert.ToInt32(sLine.Substring(0, iCharEndIndex).Trim());
                                else
                                    continue;

                                // drop off prior field contents
                                sLine = sLine.Substring(iCharEndIndex).Trim();
                                if (sLine.Length == 0)
                                    continue;
                                // find end of next column
                                iCharEndIndex = sLine.IndexOf("   ");
                                // fill field
                                item.Name = sLine.Substring(0, iCharEndIndex).Trim();

                                // drop off prior field contents
                                sLine = sLine.Substring(iCharEndIndex).Trim();
                                if (sLine.Length == 0)
                                    continue;
                                // find end of next column
                                iCharEndIndex = sLine.IndexOf("   ");
                                // fill field
                                item.File = sLine.Substring(0, iCharEndIndex).Trim();

                                // drop off prior field contents
                                sLine = sLine.Substring(iCharEndIndex).Trim();
                                if (sLine.Length == 0)
                                    continue;
                                // find end of next column
                                iCharEndIndex = sLine.IndexOf("   ");
                                // fill field
                                item.GuestOS = sLine.Substring(0, iCharEndIndex).Trim();

                                // drop off prior field contents
                                sLine = sLine.Substring(iCharEndIndex).Trim();
                                if (sLine.Length == 0)
                                    continue;
                                // find end of next column
                                iCharEndIndex = sLine.IndexOf("   ");
                                if (iCharEndIndex == -1)
                                    iCharEndIndex = sLine.Length;
                                // fill field
                                item.Version = sLine.Substring(0, iCharEndIndex).Trim();

                                // drop off prior field contents
                                sLine = sLine.Substring(iCharEndIndex).Trim();
                                if (sLine.Length > 0)
                                {
                                    // fill field
                                    item.Annotation = sLine.Replace("\n", "; ").Trim();
                                }
                                else
                                    item.Annotation = "";

                                hostMachines.Add(item.Name, item);
                            }
                            catch { }
                        }
                    }
                }
            }
        }
        public bool MachineExists(string MachineName)
        {
            return (hostMachines.ContainsKey(MachineName));
        }
        public void SelectMachine(string MachineName)
        {
            if(MachineName == "")
            {
                // Empty, so reset
                if( machineSelected )
                {
                    selectedMachine = "";
                    selectedMachineId = -1;
                    powerState = "";
                    assignedIp = "";
                    machineProperties = new Dictionary<string, string>();
                    machineProperties.Add("HostAddress", hostAddress);
                    machineSelected = false;
                }
            }
            if (!MachineExists(MachineName))
                throw new Exception("Unable to locate requested Machine.");

            if (selectedMachine == MachineName)
            {
                // machine already selected. do nothing
                return;
            }
            selectedMachine = MachineName;
            selectedMachineId = hostMachines[selectedMachine].Id;
            machineSelected = true;


            PopulateMachineProperties();

        }
        private void PopulateMachineProperties()
        {
            machineProperties = new Dictionary<string, string>();
            machineProperties.Add("HostAddress", hostAddress);
            machineProperties.Add("MachineName", selectedMachine);

            // Parms we are looking for
            Dictionary<string, string> rawProperties = new Dictionary<string,string>
            {
                {"powerState", "PowerState"},
                {"guestFullName", "OperatingSystem"},
                {"toolsStatus", "VMwareToolsInstalledStatus"},
                {"toolsVersionStatus", "VMwareToolsVersionStatus"},
                {"toolsRunningStatus", "VMwareToolsRunningState"},
                {"hostName", "HostName"},
                {"ipAddress", "IpAddress"},
                {"memorySizeMB", "MemoryMB"},
                {"numCpu", "CpuCount"}
            };

            // properties for a machine
            // yes, this is ugly, but hey, I'm parsing console output!!
            using (SshCommand cmd = virtualHost.RunCommand("vim-cmd vmsvc/get.summary " + selectedMachineId.ToString()))
            {
                String[] sResLines = cmd.Result.Split("\n".ToCharArray(0, 1)[0]);
                int iResLinesCount = sResLines.GetLength(0);
                if (iResLinesCount > 2)
                {

                    int lIndex = 0;
                    while (++lIndex < iResLinesCount)
                    {
                        if (sResLines[lIndex].Trim().Length > 10)
                        {
                            string sParamName = "";
                            string sParamValue = "";
                            string sLine = sResLines[lIndex].Trim();
                            // if a parameter, will end with a comma
                            if (sLine.Substring(sLine.Length - 1) != ",")
                                continue;
                            int iCharSplitterIndex = sLine.IndexOf(" = ");
                            if (iCharSplitterIndex == -1)
                                continue;

                            // grab first the value name
                            sParamName = sLine.Substring(0, iCharSplitterIndex + 1).Trim();

                            // is this a parameter we are looking for?
                            if (rawProperties.ContainsKey(sParamName))
                            {
                                string sPropName = rawProperties[sParamName];
                                sParamValue = sLine.Substring(iCharSplitterIndex + 2).Trim();

                                // cleanup value
                                if (sParamValue.Contains("<unset>"))
                                    sParamValue = "";
                                else
                                    // strip comma
                                    sParamValue = sParamValue.Substring(0, sParamValue.Length - 1).Trim();
                                if (sParamValue.Length > 1)
                                {
                                    if (sParamValue.StartsWith("\""))
                                        sParamValue = sParamValue.Substring(1, sParamValue.Length - 2).Trim();
                                    if (sParamValue.StartsWith("'"))
                                        sParamValue = sParamValue.Substring(1, sParamValue.Length - 2).Trim();
                                }

                                machineProperties.Add(sPropName, sParamValue);
                                rawProperties.Remove(sParamName);
                            }
                        }
                    }
                }
            }
            // fill any missing parameters
            if(rawProperties.Count > 0)
            {
                foreach (string sPropName in rawProperties.Values)
                    machineProperties.Add(sPropName, "");
            }
            // fill class values
            powerState = machineProperties["PowerState"];
            assignedIp = machineProperties["IpAddress"]; ;

        }
        public void RevertToSnapshot(string SnapshotName)
        {
            if (!machineSelected)
                throw new Exception("No machine is selected.");
            int iSnapshotId = GetSnapshotId(SnapshotName);
            using (
                SshCommand cmd = virtualHost.RunCommand(
                    "vim-cmd vmsvc/snapshot.revert "
                    + selectedMachineId.ToString()
                    + " "
                    + iSnapshotId.ToString()
                    + " 0 0"
                    )
                )
            { }

        }
        public void PowerOn()
        {
            if (!machineSelected)
                throw new Exception("No machine is selected.");
            string sMachineState = PowerState();
            if (sMachineState == "poweredOff")
            {
                using (
                    SshCommand cmd = virtualHost.RunCommand(
                        "vim-cmd vmsvc/power.on "
                        + selectedMachineId.ToString()
                        )
                    )
                { }
            }
        }
        public void PowerOff()
        {
            if (!machineSelected)
                throw new Exception("No machine is selected.");
            string sMachineState = PowerState();
            if (sMachineState == "poweredOn")
            {
                using (
                    SshCommand cmd = virtualHost.RunCommand(
                        "vim-cmd vmsvc/power.off "
                        + selectedMachineId.ToString()
                        )
                    )
                { }
            }
        }
        public void Shutdown()
        {
            if (!machineSelected)
                throw new Exception("No machine is selected.");
            string sMachineState = PowerState();
            if (sMachineState == "poweredOn")
            {
                using (
                    SshCommand cmd = virtualHost.RunCommand(
                        "vim-cmd vmsvc/power.shutdown "
                        + selectedMachineId.ToString()
                        )
                    )
                { }
            }
        }
        private int GetSnapshotId(string sSnapshotName)
        {
            // snapshot for a machine
            // yes, this is ugly, but hey, I'm parsing console output!!
            using (SshCommand cmd = virtualHost.RunCommand("vim-cmd vmsvc/snapshot.get " + selectedMachineId.ToString()))
            {
                String[] sResLines = cmd.Result.Split("\n".ToCharArray(0, 1)[0]);
                int iResLinesCount = sResLines.GetLength(0);
                if (iResLinesCount > 2)
                {

                    int lIndex = 0;
                    while (++lIndex < iResLinesCount)
                    {
                        if (sResLines[lIndex].Trim().Length > 10)
                        {
                            string sLine = sResLines[lIndex].Trim();
                            if (sLine.Contains("Snapshot Name"))
                            {
                                // is this the snapshot we are looking for?
                                if (!sLine.Substring(sLine.IndexOf(":") + 1).Contains(sSnapshotName))
                                    continue;

                                // next line has the ID, grab it
                                sLine = sResLines[lIndex+1].Trim();
                                sLine = sLine.Substring(sLine.IndexOf(" : ") + 3).Trim();
                                return (Convert.ToInt32(sLine));
                            }
                        }
                    }
                }
                // we didn't find the snapshot, throw an exception
                throw new Exception("Snapshot not defined.");
            }
        }
        public void Dispose()
        {
            Dispose(true);

            // Call SupressFinalize in case a subclass implements a finalizer.
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            // If you need thread safety, use a lock around these  
            // operations, as well as in your methods that use the resource. 
            if (!_disposed)
            {
                if (disposing)
                {
                    virtualHost.Disconnect();
                    virtualHost.Dispose();
                }

                // Indicate that the instance has been disposed.
                _disposed = true;
            }
        }
    }
}
